package com.k8.registry;

import com.k8.common.event.EventDeployer;
import com.k8.common.factroy.BeanFactory;
import com.k8.common.util.StrUtil;
import com.k8.registry.listener.NotifyListener;
import com.k8.rpc.registry.Registry;
import com.k8.rpc.metadata.AppMetaData;
import com.k8.rpc.url.ApiSignature;
import com.k8.rpc.url.URL;
import com.k8.rpc.util.URLUtils;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import static com.k8.common.constants.CommonConstants.*;
import static com.k8.common.constants.CommonKeyConstants.EXPORTER_KEY;
import static com.k8.common.constants.CommonKeyConstants.RegistryKeyConstant.*;
import static com.k8.common.constants.CommonKeyConstants.RpcKeyConstant.API_SIGNATURE_KEY;
import static com.k8.common.constants.CommonKeyConstants.SCHEDULE_GAP_TIME_KEY;
import static com.k8.common.constants.SystemPropertyKey.USER_HOME;

/**
 * Registry应该是一个注册中心对应一个
 * abstractRegistry相当于一个本地的注册中心，当外部注册中心出错则尝试从本地配置获取
 * 1.服务注册
 * 1.1服务注册应当支持check，如果注册中心出错应该有定时任务不断重试，知道返回future，get一定时长后还是没获取则抛出异常，且应该有日志输出异常
 * 1.2服务注册应当支持关闭服务前先从注册中心下线
 * <p>
 * 2.服务发现
 * 具体的服务发现路由规则应该交给remoting层
 * 在registry层应当做好解决异步并发订阅问题
 * 2.1. 不同于dubbo，由于k8有统一的事件发布中心，且支持同步事件处理机制，故此在异步服务发现成功后使用notify通知，进行本地缓存时，不需要临时文件锁
 * 去保证并发安全性，事件中心已经能够支持了
 */

@Slf4j
public abstract class AbstractRegistry implements Registry {
    private final List<URL> registered = Collections.synchronizedList(new LinkedList<>());

    private boolean syncSaveFile;
    private EventDeployer eventDeployer;
    protected BeanFactory beanFactory;
    private SaveToLocalContext saveToLocalContext;

    public static class SaveToLocalContext {
        public static final int MAX_RETRY_TIMES_SAVE_PROPERTIES = 3;
        private final long scheduleTime;
        private File file;

        public long getScheduleTime() {
            return scheduleTime;
        }

        public File getFile() {
            return file;
        }

        public SaveToLocalContext(long scheduleTime, File file) {
            this.scheduleTime = scheduleTime;
            this.file = file;
        }
    }

    public AbstractRegistry(URL url, BeanFactory beanFactory) {
        long scheduleTime = url.getParameter(SCHEDULE_GAP_TIME_KEY, 1000l);
        this.beanFactory = beanFactory;
        this.eventDeployer = beanFactory.getBean(EventDeployer.class);
        if (this.eventDeployer == null) throw new IllegalStateException("EventDeployer is not register in beanFactory");
        File file = null;
        String dirPath = System.getProperty(USER_HOME) + File.separator + CACHE_REGISTRY_DIR;
        String defaultFileName = dirPath + File.separator + CACHE_REGISTRY_PROPERTIES_FILE_FIX + "-" + "-"
                + url.getAddress().toString().replaceAll(":", "-").replaceAll("[/\\\\]", "") + CACHE;
        String fileName = url.getParameter(CACHE_REGISTRY_FILE_KEY, defaultFileName);
        if (StrUtil.isNotEmpty(fileName)) {
            file = new File(fileName);
            try {
                if (!Files.exists(Paths.get(dirPath))) {
                    Files.createDirectories(Paths.get(dirPath));
                }
                if (!Files.exists(Paths.get(fileName))) {
                    Files.createFile(Paths.get(fileName));
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        this.saveToLocalContext = new SaveToLocalContext(scheduleTime, file);
        syncSaveFile = url.getParameter(SYNC_SAVE_CACHE_KEY, false);
    }

    @Override
    public void subscribe(URL url) {
        if (url == null) {
            throw new IllegalArgumentException("subscribe url == null");
        }
        URL protocolUrl = null;
        try {
            protocolUrl = (URL) url.getAttribute(EXPORTER_KEY);
        } catch (RuntimeException e) {
            String urlName = protocolUrl == null ? "protocolUrl" : "registryConfigUrl";
            throw new IllegalArgumentException("This url is not have " + urlName + " , url: " + URLUtils.buildURLSimpleString(url));
        }
        ApiSignature apiSignature = (ApiSignature) url.getAttribute(API_SIGNATURE_KEY);
        List<AppMetaData> remoteAppInstances = null;
        try {
            String configure = getRemoteProtocolServiceConfigure(apiSignature, url);
            //填充protocolUrl
            restoreConfigureToURL(configure, protocolUrl);
            String mapping = getRemoteMappingOfServiceToProvider(apiSignature, url);
            remoteAppInstances = getRemoteAppInstancesAndSubscribe(mapping, apiSignature, url, protocolUrl);
            notifyListenerToStory(remoteAppInstances, syncSaveFile, apiSignature.getSignature());
        } catch (Throwable throwable) {
            //当注册中心出问题报异常时，使用本地缓存
            //todo 本地注册读
            remoteAppInstances = readStoryAppInstances(apiSignature);
        }
        url.putAttribute(SERVICE_INSTANCES_KEY, remoteAppInstances);
    }

    /**
     * 为了保证本地缓存的读写串行，所以读本地实例也是通过发布一个事件，不过该事件被标记为读
     */
    private List<AppMetaData> readStoryAppInstances(ApiSignature apiSignature) {
        List remoteAppInstances = new LinkedList();
        NotifyListener.NotifyEvent notifyEvent = new NotifyListener.NotifyEvent(remoteAppInstances, saveToLocalContext, apiSignature.getSignature(), true);
        //这里使用同步事件发布处理
        eventDeployer.publishEvent(notifyEvent);
        return remoteAppInstances;
    }

    /**
     * 保存实例到本地
     */
    public void notifyListenerToStory(List<AppMetaData> remoteAppInstances, boolean syncSaveFile, String signature) {
        log.info("Register appInstance in local.");
        NotifyListener.NotifyEvent notifyEvent = new NotifyListener.NotifyEvent(remoteAppInstances, saveToLocalContext, signature, false);
        if (syncSaveFile) {
            eventDeployer.publishEvent(notifyEvent);
        } else {
            eventDeployer.publishEventAsync(notifyEvent);
        }
    }

    //获取所有实例的metadata
    abstract protected List<AppMetaData> getRemoteAppInstancesAndSubscribe(String mapping, ApiSignature apiSignature, URL registryConfigUrl, URL baseProtocolUrl);

    abstract protected void restoreConfigureToURL(String configure, URL protocolUrl);

    abstract protected String getRemoteMappingOfServiceToProvider(ApiSignature apiSignature, URL registryConfigUrl);

    //从注册中心中拿去protocol-service配置文件configure
    abstract protected String getRemoteProtocolServiceConfigure(ApiSignature apiSignature, URL registryConfigUrl);

    @Override
    public void destroy() {

    }

    @Override
    public void registerMappingAndConfiguration(URL url) {
        this.registered.add(url);
    }

    @Override
    public void unregister(URL url) {

    }


    @Override
    public void unsubscribe(URL url) {

    }

    @Override
    public URL getUrl() {
        throw new RuntimeException("The registry has multiple URLs");
    }

    @Override
    public boolean isAvailable() {
        return false;
    }

    //consumer端查找某个reference的所有实现类
    @Override
    public List<URL> lookup(URL url) {
        return null;
    }


}
